#include <string.h>

#include "base/Counter.h"
#include "base/LoadRequest.h"
#include "base/Processor.h"
#include "base/Configuration.h"
#include "base/QuiesceRequest.h"
#include "base/QuiesceResponse.h"
#include "base/RawFlushRequest.h"
#include "base/Request.h"
#include "base/Response.h"
#include "base/Simulator.h"
#include "base/SnoopACK.h"
#include "base/SnoopRequest.h"
#include "base/SnoopResponse.h"
#include "base/StoreRequest.h"
#include "mem/InvalidateRequest.h"
#include "mem/IsDirtyRequest.h"
#include "mem/UpdateRequest.h"
#include "mem/SplitBus.h"
#include "mem/VerifyRequest.h"
#include "mem/RawMemoryRequest.h"
#include "mem/CancelRequestsRequest.h"
#include "util/Pool.h"

NEW_DELETE_OPERATORS_CPP(SplitBus,1);
NEW_DELETE_OPERATORS_CPP(AddressLockInfo,10000);
NEW_DELETE_OPERATORS_TEMPLATE_CPP(__quote__(HashTable<VA, AddressLockInfo*>), 10000);
NEW_DELETE_OPERATORS_TEMPLATE_CPP(__quote__(HashTableNode<VA, AddressLockInfo*>), 10000);
         
SplitBus::SplitBus (const char* _name,
                    bool _addrLockOnReq,
                    Logger* _busLog,
                    Counter*_statArbedStores,
                    Counter*_statArbedLoads,
                    Counter*_statArbedOther) :
    Entity(_name, CLOCK_WITH_INTERCONNECT),
    statsReqInput(NEW StatisticsCollection("ReqBus: InputStats", false)),
    statsReqOutput(NEW StatisticsCollection("ReqBus: OutputStats", false)),
    statsReqQueue(NEW StatisticsCollection("ReqBus: QueueStats", false)),
    statsResInput(NEW StatisticsCollection("ResBus: InputStats", false)),
    statsResOutput(NEW StatisticsCollection("ResBus: OutputStats", false)),
    statsResQueue(NEW StatisticsCollection("ResBus: QueueStats", false)),
    statArbedStores(_statArbedStores),
    statArbedLoads(_statArbedLoads),
    statArbedOther(_statArbedOther),
    requestBus(NULL),
    responseBus(NULL),
    addrLockOnReq(_addrLockOnReq),
    lockedAddrInfo(128, VAtoUint)
{
    if (_busLog != NULL) {
        busLog = _busLog;
    } else {
        busLog = NEW Logger("busLog", "b", "-:w,log.sim", "Bus tracing");
    }

    maxRecursion = global_cfg->getIntegerArgument("bus_max_lock_rec",
                                                  "Maximum level of lock recursion in the bus.",
                                                  2,
                                                  CONFIG_UNSIGNED);
 
    registerPacketHandler(typeid(LoadRequest),
                          (PacketHandlerPtr)&SplitBus::handle_packet);
    registerPacketHandler(typeid(StoreRequest),
                          (PacketHandlerPtr)&SplitBus::handle_packet);
    registerPacketHandler(typeid(SnoopResponse),
                          (PacketHandlerPtr)&SplitBus::handle_packet);
    registerPacketHandler(typeid(Response),
                          (PacketHandlerPtr)&SplitBus::handle_packet);

    // TODO: this needs to be generic
    registerPacketHandler(typeid(RawFlushRequest),
                          (PacketHandlerPtr)&SplitBus::handle_RawFlushRequest);
    registerPacketHandler(typeid(InvalidateRequest),
                          (PacketHandlerPtr)&SplitBus::routePacket);
    registerPacketHandler(typeid(VerifyRequest),
                          (PacketHandlerPtr)&SplitBus::routePacket);
    registerPacketHandler(typeid(UpdateRequest),
                          (PacketHandlerPtr)&SplitBus::routePacket);
    registerPacketHandler(typeid(QuiesceRequest),
                          (PacketHandlerPtr)&SplitBus::handle_QuiesceRequest);
    registerPacketHandler(typeid(QuiesceResponse),
                          (PacketHandlerPtr)&SplitBus::handle_QuiesceResponse);
    registerPacketHandler(typeid(IsDirtyRequest),
                          (PacketHandlerPtr)&SplitBus::routePacket);
    registerPacketHandler(typeid(SnoopACK),
                          (PacketHandlerPtr)&SplitBus::handle_snoopACK);
    registerPacketHandler(typeid(RawMemoryRequest),
                          (PacketHandlerPtr)&SplitBus::routePacket);
    ignorePacketType(typeid(CancelRequestsRequest));

    reqSnoopDevs = NEW List<Entity*>;
    resSnoopDevs = NEW List<Entity*>;

    quiesce     = false;
    quiesceRequest = NULL;
    outstandingStores = 0;    
}

void SplitBus::buildBuses (uint width, uint arbDelay, uint bufferSize) {
    char bname[1024];
    strncpy(bname, getName(), 1024-5);
    strcat(bname, "[rq]");
    requestBus = buildBus(bname, 
                          width, 
                          arbDelay, 
                          bufferSize,
                          statsReqInput,
                          statsReqOutput,
                          statsReqQueue);
    addRoute(requestBus, requestBus);

    strncpy(bname, getName(), 1024-5);
    strcat(bname, "[rs]");
    responseBus = buildBus(bname,
                           width,
                           arbDelay,
                           bufferSize,
                           statsResInput,
                           statsResOutput,
                           statsResQueue);
    addRoute(responseBus, responseBus);
}

Bus* SplitBus::buildBus (char* name,
                         int width,
                         int arbDelay,
                         int bufferSize,
                         StatisticsCollection* statsInput, 
                         StatisticsCollection* statsOutput, 
                         StatisticsCollection* statsQueue) 
{
    return NEW Bus(name,
                   this,
                   width,
                   arbDelay,
                   bufferSize,
                   statsInput,
                   statsOutput,
                   statsQueue,
                   busLog);
}

void SplitBus::countVotes () {
    requestBus->countVotes();
    responseBus->countVotes();
}

void SplitBus::printStatistics (FILE* f) {
    statsReqInput->printStatisticsCollection(f, "ReqInput");
    statsReqOutput->printStatisticsCollection(f, "ReqOutput");
    statsResInput->printStatisticsCollection(f, "ResInput");
    statsResOutput->printStatisticsCollection(f, "ResOutput");
    statsReqQueue->printStatisticsCollection(f, "ReqQueue");
    statsResQueue->printStatisticsCollection(f, "ResQueue");
}

void SplitBus::hookRecordTraffic (Bus* b, Packet* packet) {
    uint bytes = packet->getSize();
    if (IS_A(StoreRequest*, packet)) {
        statArbedStores->increment(bytes);
    } else if (IS_A(LoadRequest*, packet)) {
        statArbedLoads->increment(bytes);
    } else if (IS_A(Response*, packet)) {
        // responses go into the load or store bucket based on their request
        // type
        Request* req = static_cast<Response*>(packet)->getRequest();
        if (IS_A(StoreRequest*, req)) {
            statArbedStores->increment(bytes);
        } else if (IS_A(LoadRequest*, req)) {
            statArbedLoads->increment(bytes);
        } else {
            statArbedOther->increment(bytes);
        }
    } else {
        statArbedOther->increment(bytes);
    }
}

Result SplitBus::handle_packet (Packet* r) {
    if (dynamic_cast<Request*>(r)) {
        if (addrLockOnReq) {
            DataRequest* dr = dynamic_cast<DataRequest*>(r);
            if (dr != NULL) {
                VA addr = dr->getAddress();

                if (tryLock(addr, dr->getSource(), r) == stall) {
                    return stall;
                }
            }
        }
        return requestBus->handle(r);
    } else if (dynamic_cast<Response*>(r)) {
        return responseBus->handle(r);
    }
    
    ASSERT(false);
    return failure;
}

Result SplitBus::tryLock (VA addr, Entity* dev, Packet* r) {
    // check if there is an outstanding request with the same address  
    if (isAddressLocked(addr, dev, r)) {
        addToStallQueue(addr, dev, r);
        return stall;
    } else {
        // check if there is someone scheduled for the address.
        AddressLockInfo* al = lockedAddrInfo.lookup(addr);
        if (al == NULL || !al->hasSchedule()) {
            // no one is scheduled.
            lockAddress(addr, dev,r);
        } else {
            // we have a schedule.
            if (al->nextDev == dev) {
                // this is the scheduled one
                busLog->d("%s:%lli SCHEDULE APPLIED: %s %0llx\n",
                          name,
                          global_sim->getSimclock(),
                          dev->getName(),
                          addr);   
                al->nextDev = NULL;
                lockAddress(addr, dev,r);
            } else {
                // wait for your turn.
                busLog->d("%s:%lli:%s DEFERRED BY SCHEDULE(%s): %s %0llx\n",
                          name,
                          global_sim->getSimclock(),
                          r->toString(),
                          al->nextDev->getName(),
                          dev->getName(),
                          addr);                          
                addToStallQueue(addr, dev, r);
                return stall;
            }
        }
    }
    return success;
}

// an address is locked only when there is an outstanding request to
// the address.
bool SplitBus::isAddressLocked(VA addr, Entity* _dev, Packet* r) {
    List<AddressLockInfo*>::iterator iter;

    AddressLockInfo* al = lockedAddrInfo.lookup(addr);
    if (al != NULL && al->dev != NULL) {
        // check if it is locked by me.
        if (_dev == al->dev) {
           ASSERT(al->numRecursion > 0);
           if (al->recursionDisabled) {
                busLog->d("%s:%lli:%s LOCK RECURSION DISABLED FOR %s: %0llx\n",
                          name,
                          global_sim->getSimclock(),
                          r->toString(),
                          al->dev->getName(),
                          addr); 
                return true;
           }
           
           busLog->d("%s:%lli:%s ADDRESS LOCKED BY ME(%s) %0llx\n",
                     name,
                     global_sim->getSimclock(),
                     r->toString(),
                     al->dev->getName(),
                     addr); 
                         
       

           return false;
        } else {
#if 0
            busLog->d("%s:%lli:%s %s ADDRESS LOCKED BY %s: %0llx\n",
                      name,
                      global_sim->getSimclock(),
                      r->toString(),
                      _dev->getName(),
                      al->dev->getName(),
                      addr);  
#endif
           return true;
        }
    }

    busLog->d("%s:%lli:%s ADDRESS NOT LOCKED : %s %0llx\n",
              name,
              global_sim->getSimclock(),
              r->toString(),
              _dev->getName(),
              addr);  
                             
    return false;
}

void SplitBus::lockAddress(VA addr, Entity* _dev, Packet* r) {
    AddressLockInfo* al = lockedAddrInfo.lookup(addr);
    if (al == NULL) {
        al = NEW AddressLockInfo();
        lockedAddrInfo.insert(addr, al);
    }

    ASSERT(al->nextDev == NULL);
    
    if (al->dev == NULL) {
        al->dev = _dev;
        al->numRecursion = 1;
        al->recursionDisabled = false;
        busLog->d("%s:%lli:%s FIRST LOCKING (%d) : %s %0llx\n",
                  name,
                  global_sim->getSimclock(),
                  r->toString(),
                  al->numRecursion,
                  _dev->getName(),
                  addr);                       
        return;
    } else {
        ASSERT(_dev == al->dev);
        ASSERT(al->numRecursion > 0);
        al->numRecursion++;
        busLog->d("%s:%lli:%s RECURSIVE LOCKING (%d) : %s %0llx\n",
                  name,
                  global_sim->getSimclock(),
                  r->toString(),
                  al->numRecursion,
                  _dev->getName(),
                  addr);           

        if (al->numRecursion == maxRecursion) {
            disableRecursion(addr);
        } else {
            ASSERT(al->numRecursion < maxRecursion);
        }
        
        return;
    }
}

bool SplitBus::releaseLock(VA addr, Entity* _dev, const char* packetName) {
    List<AddressLockInfo*>::iterator iter;

    busLog->d("%s:%lli:%s releaseLock: %s %0llx\n",
              name,
              global_sim->getSimclock(),
              packetName,
              _dev->getName(),
              addr);  

    AddressLockInfo* al = lockedAddrInfo.lookup(addr);
    ASSERT(al != NULL);
    ASSERT(_dev == al->dev);
    ASSERT(al->nextDev == NULL);
    
    al->numRecursion--;
    if (al->numRecursion != 0) {
        busLog->d("%s:%lli:%s RECURSIVE UNLOCK (%d) : %s %0llx\n",
                  name,
                  global_sim->getSimclock(),
                  packetName,
                  al->numRecursion,
                  _dev->getName(),
                  addr);  
        return false;
    }

    ASSERT(al->numRecursion == 0);
    busLog->d("%s:%lli:%s LAST UNLOCK (%d) : %s %0llx\n",
              name,
              global_sim->getSimclock(),
              packetName,
              al->numRecursion,
              _dev->getName(),
              addr);  

    al->dev = NULL;
    al->recursionDisabled = false;
    if (al->stallQueue->empty()) {
        lockedAddrInfo.remove(addr);
        delete al; 
    } else {
        al->nextDev = al->stallQueue->remove();
        al->prevScheduleTime = global_sim->getSimclock();
        
        busLog->d("%s:%lli SCHEDULED: %s %0llx\n",
                  name,
                  global_sim->getSimclock(),
                  al->nextDev->getName(),
                  addr);             
    }
    
    return true;
}

void SplitBus::disableRecursion(VA addr) {
    List<AddressLockInfo*>::iterator iter;

    AddressLockInfo* al = lockedAddrInfo.lookup(addr);
    ASSERT(al != NULL);
    ASSERT(al->dev != NULL);
    
    al->recursionDisabled = true;
    busLog->d("%s:%lli DISABLE LOCK RECURSION FOR %s: %0llx\n",
              name,
              global_sim->getSimclock(),
              al->dev->getName(),
              addr);               
}

bool SplitBus::isHighPriority (Packet* p) {
    return false;
}

void SplitBus::addToStallQueue (VA addr, Entity* _dev, Packet* r) {
    AddressLockInfo* al = lockedAddrInfo.lookup(addr);
    ASSERT(al != NULL);
    if (al->dev == _dev) {
        ASSERT(al->recursionDisabled);
        return;
    }

    if (al->addToStallQueue(_dev, isHighPriority(r))) {
        busLog->d("%s:%lli ADDED TO STALL QUEUE[%d]: %s %0llx",
                  name,
                  global_sim->getSimclock(),
                  al->stallQueue->getLength(),
                  _dev->getName(),
                  addr);    

        List<Entity*>::iterator iter;
        for (iter = al->stallQueue->begin(); iter != al->stallQueue->end(); iter++) {
            busLog->d(" [%s]", (*iter)->getName());
        }

        busLog->d("\n");
    }
}

Result SplitBus::handle_snoopACK (Packet* r) {
    SnoopACK* snoopACK = CAST_OR_DIE(SnoopACK*, r);
    SnoopRequest* snr = CAST_OR_DIE(SnoopRequest*, snoopACK->getRequest());
    ASSERT(snr->getInterconnect() == getRequestBus()); // from caches

    // snoop ACK should be accepted by the originating bus.
    // in intel chipsets, this response message corresponses to HITM# signal.
    // Bus asserts TRDY(target ready) alont the HIT# signal.
    // then cache writes back the modfied line.
    ((Bus*)snr->getInterconnect())->handle(r);
    return success;
}

Result SplitBus::handle_RawFlushRequest (Packet* r) {
    routePacket(r);
    
    requestBus->handle(r);
    responseBus->handle(r);
    return success;
}

bool SplitBus::visitClear (VA addr, AddressLockInfo*& info, void* data) {
    delete info;
    return false;
}


SplitBus::~SplitBus () {
    delete reqSnoopDevs;
    delete resSnoopDevs;
    if (addrLockOnReq) {
        lockedAddrInfo.visit(&SplitBus::visitClear, NULL);  
    }
    delete responseBus;
    delete requestBus;
    delete statsReqInput;
    delete statsReqOutput;
    delete statsReqQueue;
    delete statsResInput;
    delete statsResOutput;
    delete statsResQueue;
    delete busLog;
}

Result SplitBus::delegateRoute (Entity* inter, Packet* r) {
    VA addr = 0;
    Entity* _dev = NULL;
    bool isNack = false;
    char* dataRequestSourceName = NULL;
    char rToString[128];
    strcpy(rToString, r->toString());
    
    if (addrLockOnReq) {
        Response* resp = dynamic_cast<Response*>(r);
        if (resp != NULL) {
            DataRequest* dr = dynamic_cast<DataRequest*>(resp->getRequest());
            if (dr != NULL) {
                addr = dr->getAddress();
                _dev = dr->getSource();
                isNack = Response::isNack(resp->getFlag());
                dataRequestSourceName = dr->getSource()->getName();

                // The following assert fails on TMLog NACK_ABORT messages
                // ASSERT(inter == getResponseBus());
            }
        }
    }
    
    Result ret = routePacket(r);
    // r has been freed by Cache::handle_Response
    r = NULL;
    if (addr != 0 && ret == success) {
        // check if there is an outstanding request with the same address  
        bool lockReleased = releaseLock(addr, _dev, rToString);

        if (lockReleased) {
            busLog->d("%s:%lli:%s ADDRESS LOCKING RELEASED : %s %0llx\n",
                      name,
                      global_sim->getSimclock(),
                      rToString,
                      dataRequestSourceName,
                      addr);                               
        } else {
            // we have to prevent the retry request from occupying the bus
            if (isNack) {
                disableRecursion(addr);
            }
        }
    }

    return ret;
}

Bus* SplitBus::getRequestBus () {
    return requestBus;
}

Bus* SplitBus::getResponseBus () {
    return responseBus;
}

void SplitBus::registerRequestSnoopDev (Entity* e) {
    List<Entity*>::iterator iter;
    for (iter = reqSnoopDevs->begin(); iter != reqSnoopDevs->end(); iter++) {
        Entity* _e = *iter;
        if (e == _e) {
            ASSERT(false);
        }
    }
    reqSnoopDevs->add(e);
}

void SplitBus::registerResponseSnoopDev (Entity* e) {
    List<Entity*>::iterator iter;
    for (iter = resSnoopDevs->begin(); iter != resSnoopDevs->end(); iter++) {
        Entity* _e = *iter;
        if (e == _e) {
            ASSERT(false);
        }
    }
    resSnoopDevs->add(e);
}

void SplitBus::snoop (Entity* inter, Packet* pk) {
    List<Entity*>* l;
    if (inter == getRequestBus()) {
        l = reqSnoopDevs;
    } else if (inter == getResponseBus()) {
        l = resSnoopDevs;
    } else {
        l = NULL;
        ASSERT(false);
    }

    for (List<Entity*>::iterator iter = l->begin(); 
         iter != l->end(); 
         iter++) 
    {
        Entity* e = *iter;
        if (e == pk->getSource() || pk->getSource()->onRoute(this, e)) {
            continue;
        }

        SnoopRequest* sr = NEW SnoopRequest(this, e);
        sr->setPacket(pk);
        sr->setInterconnect(inter);

        routePacket(sr);
    }
}

bool SplitBus::visitCheckSchedule (VA addr,
                                   AddressLockInfo*& al,
                                   void* data)
{
    SplitBus* splitbus = (SplitBus*)data;
    Logger* busLog = splitbus->getLogger();
    
    if (al->nextDev != NULL) {
        ASSERT(al->dev == NULL);
        if (global_sim->getSimclock() - al->prevScheduleTime > PRIORITY_TIMEOUT)
        {
            al->nextDev = NULL;
            busLog->d("%s:%lli SCHEDULE CANCELLED: %0llx\n",
                     splitbus->getName(),
                     global_sim->getSimclock(),
                     addr);  

            if (!al->stallQueue->empty()) {
                al->nextDev = al->stallQueue->remove();
                al->prevScheduleTime = global_sim->getSimclock();
            
                busLog->d("%s:%lli SCHEDULED: %s %0llx\n",
                          splitbus->getName(),
                          global_sim->getSimclock(),
                          al->nextDev->getName(),
                          addr);  
            } else {
                delete al;
                return false;
            }
        }
    } else {
        ASSERT(al->dev != NULL);
    }

    return true;
}

void SplitBus::tick () {
    if (quiesce) {
        // check to see if we need to let the guy above us know that we're
        // quiesced...

        if (isReadyForQuiesce()) {
            if(quiesceRequest != NULL) {
                QuiesceRequest* rq = quiesceRequest;
                quiesceRequest = NULL;
                sendQuiesceRequest(rq);
            }
        } else {
            quiesceLog->d("%s:%lli outstandingStores(%i)\n",
                          getName(),
                          global_sim->getSimclock(),
                          outstandingStores);
        }
    }

    if (lockedAddrInfo.getLength() != 0) {
        lockedAddrInfo.visit(&SplitBus::visitCheckSchedule, (void*)this);             
    }
    
    responseBus->tick();
    requestBus->tick();
}

bool SplitBus::isSnooping (Entity* inter) {
    if (inter == getRequestBus()) {
        return reqSnoopDevs->getLength() != 0;
    } else if (inter == getResponseBus()) {
        return resSnoopDevs->getLength() != 0;
    } else {
        ASSERT(false);
    }
    return false;
}

uint SplitBus::getNumSnoopDevs(Entity* inter, Packet* p) {
    if (inter == getRequestBus()) {
        return reqSnoopDevs->getLength();
    } else if (inter == getResponseBus()) {
        return resSnoopDevs->getLength();
    } else {
        ASSERT(false);
    }
    return 0;
}

Logger* SplitBus::getLogger() {
    return busLog;
}

Entity* SplitBus::getRoutingEntity() {
    return this;
}

Result SplitBus::handle_QuiesceRequest (Packet* r) {
    incQuiesceReqCount();

    if (quiesce) {
        return handleDuplicateQuiesceRequest(r);
    }    

    ASSERT(quiesceRequest == NULL);
    
    quiesce = true;
    
    if (isReadyForQuiesce()) {
        sendQuiesceRequest((QuiesceRequest*)r);
    } else {
        quiesceRequest = (QuiesceRequest*)r;
    }
    
    return success;
}

bool SplitBus::isReadyForQuiesce () {
    return outstandingStores == 0 &&
        numQuiesceReqArrived == global_sim->getNumQuiescingEntities();
}

Result SplitBus::handleDuplicateQuiesceRequest (Packet* r) {
    // a shared cache receives a multiple quiesce request.
    // return the request immediately except the first.

    QuiesceRequest* qreq = CAST_OR_DIE(QuiesceRequest*, r);
    QuiesceResponse* qr = NEW QuiesceResponse(qreq);
    Result res = routePacket(qr);
    ASSERT(res == success);
    return success;
}

Result SplitBus::handle_QuiesceResponse (Packet* r) {
    quiesceLog->d("SplitBus::handle_QuiesceResponse\n");
    quiesce = false;
    quiesceRequest = NULL;    

    Result res = routePacket(r);
    ASSERT(res == success);
    
    return success;
}

void SplitBus::incOutstandingStores() {
    outstandingStores++;
}

void SplitBus::decOutstandingStores() {
    outstandingStores--;
}

void SplitBus::sendQuiesceRequest(QuiesceRequest* qr) {
    quiesceLog->d("%s:%lli: QUIESCED.\n",
                  name,
                  global_sim->getSimclock());

    Result res = routePacket(qr);
    ASSERT(res == success);  
}

uint SplitBus::numQuiesceReqArrived = 0;

void SplitBus::incQuiesceReqCount() {
    ASSERT(numQuiesceReqArrived >= 0);
    numQuiesceReqArrived++;
    quiesceLog->d("SplitBus:%lli: quiesce request has arrived.(%u out of %u)\n",
                  global_sim->getSimclock(),
                  numQuiesceReqArrived,
                  global_sim->getNumQuiescingEntities());
    ASSERT(numQuiesceReqArrived <= global_sim->getNumQuiescingEntities());    
}

void SplitBus::resetQuiesceReqCount() {
    numQuiesceReqArrived = 0;
}


